package com.pdg.android.stockmarket;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Random;
import java.util.Set;

import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import com.pdg.android.common.GameDataBase;

public class GameData extends GameDataBase<GameState>{

	public static final int[] GAME_LENGTHS = {5,10,20,60};
	public static final int GAME_LENGTH_SHORT_ID = 0;
	public static final int GAME_LENGTH_MEDIUM_ID = 1;
	public static final int GAME_LENGTH_LONG_ID = 2;
	public static final int GAME_LENGTH_EXTRA_LONG_ID = 3;
	public static final int GAME_LENGTH_ID_FIRST = 0;
	public static final int GAME_LENGTH_ID_COUNT = 4;
	private static final double[] MULTIPLIERS = {0,0.3333333,0.5,0.6666666,0.75,1,1.3333333,1.5,2,3,4};
	private static final int DIE_SIZE = 6;
	private static final int DIE_COUNT = 2;
	private static final String KEY_GAME_IN_PROGRESS = "game_in_progress";
	private static final boolean DEFAULT_GAME_IN_PROGRESS = false;
	private static final String KEY_DEAD_STOCK_NAMES = "dead_stock_names";
	private static final String DEFAULT_DEAD_STOCK_NAMES = "";
	private static final String STOCK_SPLITTER = ",";
	private static final double DEFAULT_PLAYER_MONEY = 1000;
	private static final int DEFAULT_GAME_TURN = 0;
	public static final int STOCK_COUNT = 10;
	private static final String KEY_IN_USE_STOCK_NAMES = "in_use_stock_names";
	private static final String DEFAULT_IN_USE_STOCK_NAMES = "";
	private static final double DEFAULT_STOCK_VALUE = 100;
	private static final double DEFAULT_STOCK_PREVIOUS_VALUE = 100;
	private static final int DEFAULT_STOCK_SHARES = 0;
	private static final String STOCK_NAME_FORMAT = "%1$s %2$s %3$s";
	private static final int ROLL_BASE = 0;
	private static final int DIE_FIRST = 0;
	public static final int STOCK_FIRST = 0;
	private static final String DEFAULT_LAST_TURN_REPORT = "";
	private static final double BANKRUPT_VALUE = 0;
	private static final String KEY_HIGH_SCORE_FORMAT = "high_score_%d";
	private static final float DEFAULT_HIGH_SCORE = 0.0f;
	private static final String KEY_TOTAL_SCORE_FORMAT = "total_score_%d";
	private static final float DEFAULT_TOTAL_SCORE = 0.0f;
	private static final String KEY_GAME_COUNT_FORMAT = "game_count_%d";
	private static final int DEFAULT_GAME_COUNT = 0;
	private static final String KEY_LAST_TURN_REPORT = "last_turn_report";
	private static final String KEY_STOCK_NAME_FORMAT = "stock_name_%d";
	private static final String KEY_STOCK_VALUE_FORMAT = "stock_value_%d";
	private static final String KEY_STOCK_PREVIOUS_VALUE_FORMAT = "stock_previous_value_%d";
	private static final String KEY_STOCK_SHARES_FORMAT = "stock_shares_%d";
	private static final String DEFAULT_STOCK_NAME = "";
	private static final String KEY_GAME_TURN = "game_turn";
	private static final String KEY_PLAYER_MONEY = "player_money";
	private static final String KEY_GAME_LENGTH = "game_length";
	private static final int DEFAULT_CURRENT_STOCK = STOCK_FIRST;
	private static final String KEY_CURRENT_STOCK = "current_stock";
	private static final int DEFAULT_GAME_LENGTH = GAME_LENGTHS[GAME_LENGTH_ID_FIRST];
	private static final String KEY_GAME_LENGTH_ID = "game_length_id";
	private static final int DEFAULT_GAME_LENGTH_ID = GAME_LENGTH_ID_FIRST;

	private String[] stockPrefixes;
	private String[] stockMiddles;
	private String[] stockSuffixes;
	private Random random = new Random();
	
	private boolean gameInProgress;
	private Set<String> deadStockNames = new HashSet<String>();
	private Set<String> inUseStockNames = new HashSet<String>();
	private double[] highScores = {0,0,0,0};
	private double[] totalScores = {0,0,0,0};
	private int[] gameCounts = {0,0,0,0};
	private String lastTurnReport = DEFAULT_LAST_TURN_REPORT;
	private Stock[] stocks = new Stock[STOCK_COUNT];

	private int gameTurn;
	private double playerMoney;
	private int gameLength;
	private int currentStock = STOCK_FIRST;
	private int gameLengthId;
	
	public double generateMultiplier(){
		int theRoll = ROLL_BASE;
		for(int theDie=DIE_FIRST;theDie<DIE_COUNT;++theDie){
			theRoll += random.nextInt(DIE_SIZE);
		}
		return MULTIPLIERS[theRoll];
	}
	
	public Stock[] getStocks(){
		return stocks;
	}
	
	public int getGameTurn(){
		return gameTurn;
	}
	
	public double getPlayerMoney(){
		return playerMoney;
	}
	
	public int getGameLength(){
		return gameLength;
	}
	
	public void newGame(int theGameLengthId,Context theContext){
		initializeStocks();
		initializePlayer(theGameLengthId);
		setGameInProgress(true);
		startNextTurn(theContext);
	}

	private void initializeInUseStockNames() {
		inUseStockNames.clear();
	}

	private void initializePlayer(int theGameLengthId) {
		setGameLengthId(theGameLengthId);
		setGameLength(GAME_LENGTHS[theGameLengthId]);
		setPlayerMoney(DEFAULT_PLAYER_MONEY);
		setGameTurn(DEFAULT_GAME_TURN);
	}

	private void setGameLengthId(int theGameLengthId) {
		gameLengthId = theGameLengthId;
	}
	
	private int getGameLengthId(){
		return gameLengthId;
	}

	private void setGameTurn(int theGameTurn) {
		gameTurn = theGameTurn;
	}

	public void setPlayerMoney(double thePlayerMoney) {
		playerMoney = thePlayerMoney;
	}

	private void setGameLength(int theGameLength) {
		gameLength = theGameLength;
	}
	
	private Stock generateStock(String theName,double theValue,double thePreviousValue,int theShares){
		Stock theStock = new Stock();
		theStock.setName(theName);
		theStock.setValue(theValue);
		theStock.setPreviousValue(thePreviousValue);
		theStock.setShares(theShares);
		return theStock;
	}
	
	private Stock generateNewStock(){
		return generateStock(generateAvailableStockName(),DEFAULT_STOCK_VALUE,DEFAULT_STOCK_PREVIOUS_VALUE,DEFAULT_STOCK_SHARES);
	}
	
	private String generateAvailableStockName(){
		String theName;
		do{
			theName = generateStockName();
		}while(!isStockNameAvailable(theName));
		return theName;
	}

	private void initializeStocks() {
		initializeDeadStocksNames();
		initializeInUseStockNames();
		for(int theIndex=STOCK_FIRST;theIndex<STOCK_COUNT;++theIndex){
			putStock(theIndex,generateNewStock());
		}
	}

	private String generateStockName() {
		int prefix = random.nextInt(getStockPrefixes().length);
		int middle = random.nextInt(getStockMiddles().length);
		int suffix = random.nextInt(getStockSuffixes().length);
		return String.format(STOCK_NAME_FORMAT,
				getStockPrefixes()[prefix],
				getStockMiddles()[middle],
				getStockSuffixes()[suffix]
				);
	}

	private void initializeDeadStocksNames() {
		deadStockNames.clear();
	}

	@Override
	protected GameState getDefaultCurrentState() {
		return GameState.MAIN_MENU;
	}

	@Override
	protected GameState decodeCurrentState(String theValue) {
		return GameState.valueOf(theValue);
	}
	protected String encodeCurrentState(GameState theValue){
		return theValue.name();
	}

	private void decodeStockNames(Set<String> stockNames, String theValue){
		stockNames.clear();
		if(theValue.length()>0){
			String[] theStocks = theValue.split(STOCK_SPLITTER);
			for(int theIndex=0;theIndex<theStocks.length;++theIndex){
				stockNames.add(theStocks[theIndex]);
			}
		}
	}

	@Override
	protected void onLoad(SharedPreferences thePreferences) {
		setGameInProgress(thePreferences.getBoolean(KEY_GAME_IN_PROGRESS, DEFAULT_GAME_IN_PROGRESS));
		decodeDeadStockNames(thePreferences.getString(KEY_DEAD_STOCK_NAMES, DEFAULT_DEAD_STOCK_NAMES));
		decodeInUseStockNames(thePreferences.getString(KEY_IN_USE_STOCK_NAMES, DEFAULT_IN_USE_STOCK_NAMES));
		setLastTurnReport(thePreferences.getString(KEY_LAST_TURN_REPORT, DEFAULT_LAST_TURN_REPORT));
		setGameTurn(thePreferences.getInt(KEY_GAME_TURN,DEFAULT_GAME_TURN));
		setPlayerMoney((double)thePreferences.getFloat(KEY_PLAYER_MONEY,(float) DEFAULT_PLAYER_MONEY));
		setGameLength(thePreferences.getInt(KEY_GAME_LENGTH, DEFAULT_GAME_LENGTH));
		setCurrentStock(thePreferences.getInt(KEY_CURRENT_STOCK,DEFAULT_CURRENT_STOCK));
		setGameLengthId(thePreferences.getInt(KEY_GAME_LENGTH_ID,DEFAULT_GAME_LENGTH_ID));
		for(int theIndex=GAME_LENGTH_ID_FIRST;theIndex<GAME_LENGTH_ID_COUNT;++theIndex){
			setHighScore(theIndex,(double)thePreferences.getFloat(String.format(KEY_HIGH_SCORE_FORMAT,theIndex), DEFAULT_HIGH_SCORE));
			setTotalScore(theIndex,(double)thePreferences.getFloat(String.format(KEY_TOTAL_SCORE_FORMAT,theIndex), DEFAULT_TOTAL_SCORE));
			setGameCount(theIndex,thePreferences.getInt(String.format(KEY_GAME_COUNT_FORMAT,theIndex), DEFAULT_GAME_COUNT));
		}
		for(int theIndex=STOCK_FIRST;theIndex<STOCK_COUNT;++theIndex){
			Stock theStock = getStocks()[theIndex];
			if(theStock==null){
				theStock=new Stock();
				getStocks()[theIndex]=theStock;
			}
			String theName = thePreferences.getString(String.format(KEY_STOCK_NAME_FORMAT,theIndex), DEFAULT_STOCK_NAME);
			double theValue= (double)thePreferences.getFloat(String.format(KEY_STOCK_VALUE_FORMAT,theIndex), (float) DEFAULT_STOCK_VALUE);
			double thePreviousValue= (double)thePreferences.getFloat(String.format(KEY_STOCK_PREVIOUS_VALUE_FORMAT,theIndex), (float) DEFAULT_STOCK_PREVIOUS_VALUE);
			int theShares= thePreferences.getInt(String.format(KEY_STOCK_SHARES_FORMAT,theIndex), DEFAULT_STOCK_SHARES);
			theStock.setName(theName);
			theStock.setValue(theValue);
			theStock.setPreviousValue(thePreviousValue);
			theStock.setShares(theShares);
		}
	}
	
	@Override
	protected void onCommit(Editor theEditor) {
		theEditor.putBoolean(KEY_GAME_IN_PROGRESS, isGameInProgress());
		theEditor.putString(KEY_DEAD_STOCK_NAMES,encodeDeadStockNames());
		theEditor.putString(KEY_IN_USE_STOCK_NAMES,encodeInUseStockNames());
		theEditor.putString(KEY_LAST_TURN_REPORT, getLastTurnReport());
		theEditor.putInt(KEY_GAME_TURN, getGameTurn());
		theEditor.putFloat(KEY_PLAYER_MONEY, (float)getPlayerMoney());
		theEditor.putInt(KEY_GAME_LENGTH, getGameLength());
		theEditor.putInt(KEY_CURRENT_STOCK, getCurrentStock());
		theEditor.putInt(KEY_GAME_LENGTH_ID, getGameLengthId());
		for(int theIndex=GAME_LENGTH_ID_FIRST;theIndex<GAME_LENGTH_ID_COUNT;++theIndex){
			theEditor.putFloat(String.format(KEY_HIGH_SCORE_FORMAT,theIndex), (float)getHighScore(theIndex));
			theEditor.putFloat(String.format(KEY_TOTAL_SCORE_FORMAT,theIndex), (float)getTotalScore(theIndex));
			theEditor.putInt(String.format(KEY_GAME_COUNT_FORMAT,theIndex), getGameCount(theIndex));
		}
		for(int theIndex=STOCK_FIRST;theIndex<STOCK_COUNT;++theIndex){
			Stock theStock = getStocks()[theIndex];
			if(theStock!=null){
				theEditor.putString(String.format(KEY_STOCK_NAME_FORMAT,theIndex), theStock.getName());
				theEditor.putFloat(String.format(KEY_STOCK_VALUE_FORMAT,theIndex), (float) theStock.getValue());
				theEditor.putFloat(String.format(KEY_STOCK_PREVIOUS_VALUE_FORMAT,theIndex), (float) theStock.getPreviousValue());
				theEditor.putInt(String.format(KEY_STOCK_SHARES_FORMAT,theIndex), theStock.getShares());
			}
		}
	}
	
	private void setGameCount(int theGameLengthId, int theGameCount) {
		gameCounts[theGameLengthId]=theGameCount;
	}

	private void setTotalScore(int theGameLengthId, double theTotalScore) {
		totalScores[theGameLengthId]=theTotalScore;
	}

	private void setHighScore(int theGameLengthId, double theHighScore) {
		highScores[theGameLengthId]=theHighScore;
	}

	public int getGameCount(int theIndex) {
		return gameCounts[theIndex];
	}

	private double getTotalScore(int theIndex) {
		return totalScores[theIndex];
	}

	private String encodeStockNames(Set<String> theStockNames){
		String theResult = null;
		Iterator<String> iterator = theStockNames.iterator();
		while(iterator.hasNext()){
			if(theResult == null){
				theResult = iterator.next();
			}else{
				theResult += STOCK_SPLITTER;
				theResult += iterator.next();
			}
		}
		return theResult;
	}

	private String encodeDeadStockNames() {
		return encodeStockNames(deadStockNames);
	}
	
	private void decodeDeadStockNames(String theValue) {
		decodeStockNames(deadStockNames,theValue);
	}

	private String encodeInUseStockNames(){
		return encodeStockNames(inUseStockNames);
		
	}
	
	private void decodeInUseStockNames(String theValue){
		decodeStockNames(inUseStockNames,theValue);
	}

	public void setGameInProgress(boolean theValue) {
		gameInProgress = theValue;
	}

	public boolean isGameInProgress() {
		return gameInProgress;
	}
	
	public boolean isStockNameAvailable(String theName){
		return !isStockNameInUse(theName) && !isStockNameDead(theName);
	}
	
	public boolean isStockNameInUse(String theName){
		return inUseStockNames.contains(theName);
	}
	
	public void addInUseStockName(String theName){
		inUseStockNames.add(theName);
	}
	
	public void removeInUseStockName(String theName){
		inUseStockNames.remove(theName);
	}
	
	public boolean isStockNameDead(String theName){
		return deadStockNames.contains(theName);
		
	}
	public void addDeadStockName(String theName){
		deadStockNames.add(theName);
	}

	public String[] getStockPrefixes() {
		return stockPrefixes;
	}

	public void setStockPrefixes(String[] deadStockPrefixes) {
		this.stockPrefixes = deadStockPrefixes;
	}

	public String[] getStockMiddles() {
		return stockMiddles;
	}

	public void setStockMiddles(String[] deadStockMiddles) {
		this.stockMiddles = deadStockMiddles;
	}

	public String[] getStockSuffixes() {
		return stockSuffixes;
	}

	public void setStockSuffixes(String[] deadStockSuffixes) {
		this.stockSuffixes = deadStockSuffixes;
	}
	private void startNextTurn(Context theContext){
		String theReport = DEFAULT_LAST_TURN_REPORT;
		//clean up bankruptcies
		Set<Integer> bankruptcies = new HashSet<Integer>();
		Set<Integer> nonbankruptcies = new HashSet<Integer>();
		for(int theIndex=STOCK_FIRST;theIndex<STOCK_COUNT;++theIndex){
			Stock theStock = stocks[theIndex];
			if(theStock.getValue()==BANKRUPT_VALUE){
				bankruptcies.add(theIndex);
				theReport+=String.format(
						theContext.getString(R.string.report_bankrupt_line),
						theStock.getName()
						);
				if(theStock.getShares()>DEFAULT_STOCK_SHARES){
					theReport+=String.format(
							theContext.getString(R.string.report_lost_shares_line),
							theStock.getShares()
							);
				}
			}else{
				nonbankruptcies.add(theIndex);
			}
		}
		Iterator<Integer> iterator = bankruptcies.iterator();
		while(iterator.hasNext()){
			Integer theIndex = iterator.next();
			Stock theStock = getStock(theIndex);
			addDeadStockName(theStock.getName());
			theStock = generateNewStock();
			putStock(theIndex,theStock);
			theReport += String.format(
					theContext.getString(R.string.report_new_stock_line),
					theStock.getName(),
					theStock.getValue()
					);
		}
		iterator = nonbankruptcies.iterator();
		while(iterator.hasNext()){
			Integer theIndex = iterator.next();
			Stock theStock = getStock(theIndex);
			if(theStock.getPreviousValue()!=theStock.getValue()){
				theReport += String.format(
						theContext.getString(R.string.report_stock_value_line),
						theStock.getName(),
						theStock.getPreviousValue(),
						theStock.getValue()
						);
			}else{
				theReport += String.format(
						theContext.getString(R.string.report_stock_value_steady_line),
						theStock.getName(),
						theStock.getValue()
						);
			}
		}
		theReport += String.format(
				theContext.getString(R.string.report_net_worth_line),
				getNetWorth()
				);		
		setGameTurn(getGameTurn()+1);
		setGameInProgress(getGameTurn()<=getGameLength());
		if(!isGameInProgress()){
			theReport += recordEndOfGame(theContext,getGameLengthId(),getNetWorth());
		}
		setLastTurnReport(theReport);
	}

	private String recordEndOfGame(Context theContext, int theGameLengthId, double theFinalScore) {
		String theResult=theContext.getString(R.string.empty_string);
		if(theFinalScore>getHighScore(theGameLengthId)){
			theResult += theContext.getString(R.string.new_high_score);
			setHighScore(theGameLengthId,theFinalScore);
		}
		double theOldAverage = getAverageScore(theGameLengthId);
		setTotalScore(theGameLengthId,getTotalScore(theGameLengthId)+theFinalScore);
		setGameCount(theGameLengthId,getGameCount(theGameLengthId)+1);
		double theNewAverage = getAverageScore(theGameLengthId);
		if(!Double.isNaN(theOldAverage)){
			if(theNewAverage>theOldAverage){
				theResult += theContext.getString(R.string.above_average_game);
			}else if(theNewAverage<theOldAverage){
				theResult += theContext.getString(R.string.below_average_game);
			}else{
				theResult += theContext.getString(R.string.average_game);
			}
		}
		return theResult;
	}

	public double getAverageScore(int theGameLengthId) {
		if(getGameCount(theGameLengthId)==0){
			return Double.NaN;
		}else{
			return getTotalScore(theGameLengthId)/getGameCount(theGameLengthId);
		}
	}

	public double getHighScore(int theGameLengthId) {
		return highScores [theGameLengthId];
	}

	private double getNetWorth() {
		double result = getPlayerMoney();
		for(int theIndex=STOCK_FIRST;theIndex<STOCK_COUNT;++theIndex){
			Stock theStock = getStocks()[theIndex];
			result += theStock.getShares() * theStock.getValue();
		}
		return result;
	}

	private Stock getStock(Integer theIndex) {
		return stocks[theIndex];
	}
	private void putStock(Integer theIndex,Stock theStock){
		stocks[theIndex]=theStock;
	}

	public void endTurn(Context theContext){
		for(int theIndex=STOCK_FIRST;theIndex<STOCK_COUNT;++theIndex){
			Stock theStock = getStocks()[theIndex];
			int theRoll = ROLL_BASE;
			for(int theDie= DIE_FIRST;theDie<DIE_COUNT;++theDie){
				theRoll += random.nextInt(DIE_SIZE);
			}
			double theMultiplier = MULTIPLIERS[theRoll];
			theStock.setPreviousValue(theStock.getValue());
			theStock.setValue(theStock.getValue()*theMultiplier);
		}
		startNextTurn(theContext);
	}

	public String getLastTurnReport() {
		return lastTurnReport;
	}

	public void setLastTurnReport(String lastTurnReport) {
		this.lastTurnReport = lastTurnReport;
	}
	
	public void appendLastTurnReport(String theAppendText){
		setLastTurnReport(getLastTurnReport()+theAppendText);
	}

	public int getCurrentStock() {
		return currentStock;
	}

	public void setCurrentStock(int currentStock) {
		this.currentStock = currentStock;
	}

	public int getMaximumPurchase() {
		return (int)Math.floor(getPlayerMoney()/getStocks()[getCurrentStock()].getValue());
	}

	public void buyStock(int theShares){
		Stock theStock = getStocks()[getCurrentStock()];
		double thePrice = theShares * theStock.getValue();
		theStock.setShares(theStock.getShares()+theShares);
		setPlayerMoney(getPlayerMoney()-thePrice);
	}
	
	public void buyMaximumStock(){
		buyStock(getMaximumPurchase());
	}

	public int getMaximumSale() {
		return getStocks()[getCurrentStock()].getShares();
	}

	public void sellMaximumStock() {
		sellStock(getMaximumSale());
	}

	public void sellStock(int theShares) {
		Stock theStock = getStocks()[getCurrentStock()];
		double thePrice = theShares * theStock.getValue();
		theStock.setShares(theStock.getShares()-theShares);
		setPlayerMoney(getPlayerMoney()+thePrice);
	}

	public void clearStatistics() {
		for(int theIndex = GAME_LENGTH_ID_FIRST;theIndex<GAME_LENGTH_ID_COUNT;++theIndex){
			setHighScore(theIndex,DEFAULT_HIGH_SCORE);
			setTotalScore(theIndex,DEFAULT_TOTAL_SCORE);
			setGameCount(theIndex,DEFAULT_GAME_COUNT);
		}
	}
	public String formatStatistics(Context theContext){
		String theResult = theContext.getString(R.string.empty_string);
		for(int theIndex = GAME_LENGTH_ID_FIRST;theIndex<GAME_LENGTH_ID_COUNT;++theIndex){
			theResult += String.format(theContext.getString(R.string.statistics_text_format),
					theContext.getResources().getStringArray(R.array.game_length_names)[theIndex],
					getHighScore(theIndex),
					getGameCount(theIndex),
					getAverageScore(theIndex)
					);
		}
		return theResult;
	}
}
